1   /*
2    * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.print;
27  
28  import java.io.FilePermission;
29  
30  import java.awt.Color;
31  import java.awt.Dialog;
32  import java.awt.Frame;
33  import java.awt.Graphics;
34  import java.awt.Graphics2D;
35  import java.awt.GraphicsConfiguration;
36  import java.awt.GraphicsEnvironment;
37  import java.awt.HeadlessException;
38  import java.awt.KeyboardFocusManager;
39  import java.awt.Rectangle;
40  import java.awt.Shape;
41  import java.awt.geom.AffineTransform;
42  import java.awt.geom.Area;
43  import java.awt.geom.Point2D;
44  import java.awt.geom.Rectangle2D;
45  import java.awt.image.BufferedImage;
46  import java.awt.print.Book;
47  import java.awt.print.Pageable;
48  import java.awt.print.PageFormat;
49  import java.awt.print.Paper;
50  import java.awt.print.Printable;
51  import java.awt.print.PrinterAbortException;
52  import java.awt.print.PrinterException;
53  import java.awt.print.PrinterJob;
54  import java.awt.Window;
55  import java.io.File;
56  import java.io.IOException;
57  import java.util.ArrayList;
58  import java.util.Enumeration;
59  import java.util.Locale;
60  import sun.awt.image.ByteInterleavedRaster;
61  
62  import javax.print.Doc;
63  import javax.print.DocFlavor;
64  import javax.print.DocPrintJob;
65  import javax.print.PrintException;
66  import javax.print.PrintService;
67  import javax.print.PrintServiceLookup;
68  import javax.print.ServiceUI;
69  import javax.print.StreamPrintService;
70  import javax.print.StreamPrintServiceFactory;
71  import javax.print.attribute.Attribute;
72  import javax.print.attribute.AttributeSet;
73  import javax.print.attribute.HashPrintRequestAttributeSet;
74  import javax.print.attribute.PrintRequestAttributeSet;
75  import javax.print.attribute.Size2DSyntax;
76  import javax.print.attribute.standard.Chromaticity;
77  import javax.print.attribute.standard.Copies;
78  import javax.print.attribute.standard.Destination;
79  import javax.print.attribute.standard.DialogTypeSelection;
80  import javax.print.attribute.standard.Fidelity;
81  import javax.print.attribute.standard.JobName;
82  import javax.print.attribute.standard.JobSheets;
83  import javax.print.attribute.standard.Media;
84  import javax.print.attribute.standard.MediaPrintableArea;
85  import javax.print.attribute.standard.MediaSize;
86  import javax.print.attribute.standard.MediaSizeName;
87  import javax.print.attribute.standard.OrientationRequested;
88  import javax.print.attribute.standard.PageRanges;
89  import javax.print.attribute.standard.PrinterState;
90  import javax.print.attribute.standard.PrinterStateReason;
91  import javax.print.attribute.standard.PrinterStateReasons;
92  import javax.print.attribute.standard.PrinterIsAcceptingJobs;
93  import javax.print.attribute.standard.RequestingUserName;
94  import javax.print.attribute.standard.SheetCollate;
95  import javax.print.attribute.standard.Sides;
96  
97  import sun.print.PageableDoc;
98  import sun.print.ServiceDialog;
99  import sun.print.SunPrinterJobService;
100 import sun.print.SunPageSelection;
101 
102 /**
103  * A class which rasterizes a printer job.
104  *
105  * @author Richard Blanchard
106  */
107 public abstract class RasterPrinterJob extends PrinterJob {
108 
109  /* Class Constants */
110 
111      /* Printer destination type. */
112     protected static final int PRINTER = 0;
113 
114      /* File destination type.  */
115     protected static final int FILE = 1;
116 
117     /* Stream destination type.  */
118     protected static final int STREAM = 2;
119 
120     /**
121      * Maximum amount of memory in bytes to use for the
122      * buffered image "band". 4Mb is a compromise between
123      * limiting the number of bands on hi-res printers and
124      * not using too much of the Java heap or causing paging
125      * on systems with little RAM.
126      */
127     private static final int MAX_BAND_SIZE = (1024 * 1024 * 4);
128 
129     /* Dots Per Inch */
130     private static final float DPI = 72.0f;
131 
132     /**
133      * Useful mainly for debugging, this system property
134      * can be used to force the printing code to print
135      * using a particular pipeline. The two currently
136      * supported values are FORCE_RASTER and FORCE_PDL.
137      */
138     private static final String FORCE_PIPE_PROP = "sun.java2d.print.pipeline";
139 
140     /**
141      * When the system property FORCE_PIPE_PROP has this value
142      * then each page of a print job will be rendered through
143      * the raster pipeline.
144      */
145     private static final String FORCE_RASTER = "raster";
146 
147     /**
148      * When the system property FORCE_PIPE_PROP has this value
149      * then each page of a print job will be rendered through
150      * the PDL pipeline.
151      */
152     private static final String FORCE_PDL = "pdl";
153 
154     /**
155      * When the system property SHAPE_TEXT_PROP has this value
156      * then text is always rendered as a shape, and no attempt is made
157      * to match the font through GDI
158      */
159     private static final String SHAPE_TEXT_PROP = "sun.java2d.print.shapetext";
160 
161     /**
162      * values obtained from System properties in static initialiser block
163      */
164     public static boolean forcePDL = false;
165     public static boolean forceRaster = false;
166     public static boolean shapeTextProp = false;
167 
168     static {
169         /* The system property FORCE_PIPE_PROP
170          * can be used to force the printing code to
171          * use a particular pipeline. Either the raster
172          * pipeline or the pdl pipeline can be forced.
173          */
174         String forceStr =
175            (String)java.security.AccessController.doPrivileged(
176                    new sun.security.action.GetPropertyAction(FORCE_PIPE_PROP));
177 
178         if (forceStr != null) {
179             if (forceStr.equalsIgnoreCase(FORCE_PDL)) {
180                 forcePDL = true;
181             } else if (forceStr.equalsIgnoreCase(FORCE_RASTER)) {
182                 forceRaster = true;
183             }
184         }
185 
186         String shapeTextStr =
187            (String)java.security.AccessController.doPrivileged(
188                    new sun.security.action.GetPropertyAction(SHAPE_TEXT_PROP));
189 
190         if (shapeTextStr != null) {
191             shapeTextProp = true;
192         }
193     }
194 
195     /* Instance Variables */
196 
197     /**
198      * Used to minimise GC & reallocation of band when printing
199      */
200     private int cachedBandWidth = 0;
201     private int cachedBandHeight = 0;
202     private BufferedImage cachedBand = null;
203 
204     /**
205      * The number of book copies to be printed.
206      */
207     private int mNumCopies = 1;
208 
209     /**
210      * Collation effects the order of the pages printed
211      * when multiple copies are requested. For two copies
212      * of a three page document the page order is:
213      *  mCollate true: 1, 2, 3, 1, 2, 3
214      *  mCollate false: 1, 1, 2, 2, 3, 3
215      */
216     private boolean mCollate = false;
217 
218     /**
219      * The zero based indices of the first and last
220      * pages to be printed. If 'mFirstPage' is
221      * UNDEFINED_PAGE_NUM then the first page to
222      * be printed is page 0. If 'mLastPage' is
223      * UNDEFINED_PAGE_NUM then the last page to
224      * be printed is the last one in the book.
225      */
226     private int mFirstPage = Pageable.UNKNOWN_NUMBER_OF_PAGES;
227     private int mLastPage = Pageable.UNKNOWN_NUMBER_OF_PAGES;
228 
229     /**
230      * The previous print stream Paper
231      * Used to check if the paper size has changed such that the
232      * implementation needs to emit the new paper size information
233      * into the print stream.
234      * Since we do our own rotation, and the margins aren't relevant,
235      * Its strictly the dimensions of the paper that we will check.
236      */
237     private Paper previousPaper;
238 
239     /**
240      * The document to be printed. It is initialized to an
241      * empty (zero pages) book.
242      */
243     private Pageable mDocument = new Book();
244 
245     /**
246      * The name of the job being printed.
247      */
248     private String mDocName = "Java Printing";
249 
250 
251     /**
252      * Printing cancellation flags
253      */
254     private boolean performingPrinting = false;
255     private boolean userCancelled = false;
256 
257    /**
258     * Print to file permission variables.
259     */
260     private FilePermission printToFilePermission;
261 
262     /**
263      * List of areas & the graphics state for redrawing
264      */
265     private ArrayList redrawList = new ArrayList();
266 
267 
268     /* variables representing values extracted from an attribute set.
269      * These take precedence over values set on a printer job
270      */
271     private int copiesAttr;
272     private String jobNameAttr;
273     private String userNameAttr;
274     private PageRanges pageRangesAttr;
275     protected Sides sidesAttr;
276     protected String destinationAttr;
277     protected boolean noJobSheet = false;
278     protected int mDestType = RasterPrinterJob.FILE;
279     protected String mDestination = "";
280     protected boolean collateAttReq = false;
281 
282     /**
283      * Device rotation flag, if it support 270, this is set to true;
284      */
285     protected boolean landscapeRotates270 = false;
286 
287    /**
288      * attributes used by no-args page and print dialog and print method to
289      * communicate state
290      */
291     protected PrintRequestAttributeSet attributes = null;
292 
293     /**
294      * Class to keep state information for redrawing areas
295      * "region" is an area at as a high a resolution as possible.
296      * The redrawing code needs to look at sx, sy to calculate the scale
297      * to device resolution.
298      */
299     private class GraphicsState {
300         Rectangle2D region;  // Area of page to repaint
301         Shape theClip;       // image drawing clip.
302         AffineTransform theTransform; // to transform clip to dev coords.
303         double sx;           // X scale from region to device resolution
304         double sy;           // Y scale from region to device resolution
305     }
306 
307     /**
308      * Service for this job
309      */
310     protected PrintService myService;
311 
312  /* Constructors */
313 
314     public RasterPrinterJob()
315     {
316     }
317 
318 /* Abstract Methods */
319 
320     /**
321      * Returns the resolution in dots per inch across the width
322      * of the page.
323      */
324     abstract protected double getXRes();
325 
326     /**
327      * Returns the resolution in dots per inch down the height
328      * of the page.
329      */
330     abstract protected double getYRes();
331 
332     /**
333      * Must be obtained from the current printer.
334      * Value is in device pixels.
335      * Not adjusted for orientation of the paper.
336      */
337     abstract protected double getPhysicalPrintableX(Paper p);
338 
339     /**
340      * Must be obtained from the current printer.
341      * Value is in device pixels.
342      * Not adjusted for orientation of the paper.
343      */
344     abstract protected double getPhysicalPrintableY(Paper p);
345 
346     /**
347      * Must be obtained from the current printer.
348      * Value is in device pixels.
349      * Not adjusted for orientation of the paper.
350      */
351     abstract protected double getPhysicalPrintableWidth(Paper p);
352 
353     /**
354      * Must be obtained from the current printer.
355      * Value is in device pixels.
356      * Not adjusted for orientation of the paper.
357      */
358     abstract protected double getPhysicalPrintableHeight(Paper p);
359 
360     /**
361      * Must be obtained from the current printer.
362      * Value is in device pixels.
363      * Not adjusted for orientation of the paper.
364      */
365     abstract protected double getPhysicalPageWidth(Paper p);
366 
367     /**
368      * Must be obtained from the current printer.
369      * Value is in device pixels.
370      * Not adjusted for orientation of the paper.
371      */
372     abstract protected double getPhysicalPageHeight(Paper p);
373 
374     /**
375      * Begin a new page.
376      */
377     abstract protected void startPage(PageFormat format, Printable painter,
378                                       int index, boolean paperChanged)
379         throws PrinterException;
380 
381     /**
382      * End a page.
383      */
384     abstract protected void endPage(PageFormat format, Printable painter,
385                                     int index)
386         throws PrinterException;
387 
388     /**
389      * Prints the contents of the array of ints, 'data'
390      * to the current page. The band is placed at the
391      * location (x, y) in device coordinates on the
392      * page. The width and height of the band is
393      * specified by the caller.
394      */
395     abstract protected void printBand(byte[] data, int x, int y,
396                                       int width, int height)
397         throws PrinterException;
398 
399 /* Instance Methods */
400 
401     /**
402       * save graphics state of a PathGraphics for later redrawing
403       * of part of page represented by the region in that state
404       */
405 
406     public void saveState(AffineTransform at, Shape clip,
407                           Rectangle2D region, double sx, double sy) {
408         GraphicsState gstate = new GraphicsState();
409         gstate.theTransform = at;
410         gstate.theClip = clip;
411         gstate.region = region;
412         gstate.sx = sx;
413         gstate.sy = sy;
414         redrawList.add(gstate);
415     }
416 
417 
418     /*
419      * A convenience method which returns the default service
420      * for 2D <code>PrinterJob</code>s.
421      * May return null if there is no suitable default (although there
422      * may still be 2D services available).
423      * @return default 2D print service, or null.
424      * @since     1.4
425      */
426     protected static PrintService lookupDefaultPrintService() {
427         PrintService service = PrintServiceLookup.lookupDefaultPrintService();
428 
429         /* Pageable implies Printable so checking both isn't strictly needed */
430         if (service != null &&
431             service.isDocFlavorSupported(
432                                 DocFlavor.SERVICE_FORMATTED.PAGEABLE) &&
433             service.isDocFlavorSupported(
434                                 DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
435             return service;
436         } else {
437            PrintService []services =
438              PrintServiceLookup.lookupPrintServices(
439                                 DocFlavor.SERVICE_FORMATTED.PAGEABLE, null);
440            if (services.length > 0) {
441                return services[0];
442            }
443         }
444         return null;
445     }
446 
447    /**
448      * Returns the service (printer) for this printer job.
449      * Implementations of this class which do not support print services
450      * may return null;
451      * @return the service for this printer job.
452      *
453      */
454     public PrintService getPrintService() {
455         if (myService == null) {
456             PrintService svc = PrintServiceLookup.lookupDefaultPrintService();
457             if (svc != null &&
458                 svc.isDocFlavorSupported(
459                      DocFlavor.SERVICE_FORMATTED.PAGEABLE)) {
460                 try {
461                     setPrintService(svc);
462                     myService = svc;
463                 } catch (PrinterException e) {
464                 }
465             }
466             if (myService == null) {
467                 PrintService[] svcs = PrintServiceLookup.lookupPrintServices(
468                     DocFlavor.SERVICE_FORMATTED.PAGEABLE, null);
469                 if (svcs.length > 0) {
470                     try {
471                         setPrintService(svcs[0]);
472                         myService = svcs[0];
473                     } catch (PrinterException e) {
474                     }
475                 }
476             }
477         }
478         return myService;
479     }
480 
481     /**
482      * Associate this PrinterJob with a new PrintService.
483      *
484      * Throws <code>PrinterException</code> if the specified service
485      * cannot support the <code>Pageable</code> and
486      * <code>Printable</code> interfaces necessary to support 2D printing.
487      * @param a print service which supports 2D printing.
488      *
489      * @throws PrinterException if the specified service does not support
490      * 2D printing or no longer available.
491      */
492     public void setPrintService(PrintService service)
493         throws PrinterException {
494         if (service == null) {
495             throw new PrinterException("Service cannot be null");
496         } else if (!(service instanceof StreamPrintService) &&
497                    service.getName() == null) {
498             throw new PrinterException("Null PrintService name.");
499         } else {
500             // Check the list of services.  This service may have been
501             // deleted already
502             PrinterState prnState = (PrinterState)service.getAttribute(
503                                                   PrinterState.class);
504             if (prnState == PrinterState.STOPPED) {
505                 PrinterStateReasons prnStateReasons =
506                     (PrinterStateReasons)service.getAttribute(
507                                                  PrinterStateReasons.class);
508                 if ((prnStateReasons != null) &&
509                     (prnStateReasons.containsKey(PrinterStateReason.SHUTDOWN)))
510                 {
511                     throw new PrinterException("PrintService is no longer available.");
512                 }
513             }
514 
515 
516             if (service.isDocFlavorSupported(
517                                              DocFlavor.SERVICE_FORMATTED.PAGEABLE) &&
518                 service.isDocFlavorSupported(
519                                              DocFlavor.SERVICE_FORMATTED.PRINTABLE)) {
520                 myService = service;
521             } else {
522                 throw new PrinterException("Not a 2D print service: " + service);
523             }
524         }
525     }
526 
527 
528     protected void updatePageAttributes(PrintService service,
529                                         PageFormat page) {
530         if (service == null || page == null) {
531             return;
532         }
533 
534         float x = (float)Math.rint(
535                          (page.getPaper().getWidth()*Size2DSyntax.INCH)/
536                          (72.0))/(float)Size2DSyntax.INCH;
537         float y = (float)Math.rint(
538                          (page.getPaper().getHeight()*Size2DSyntax.INCH)/
539                          (72.0))/(float)Size2DSyntax.INCH;
540 
541         // We should limit the list where we search the matching
542         // media, this will prevent mapping to wrong media ex. Ledger
543         // can be mapped to B.  Especially useful when creating
544         // custom MediaSize.
545         Media[] mediaList = (Media[])service.getSupportedAttributeValues(
546                                       Media.class, null, null);
547         Media media = null;
548         try {
549             media = CustomMediaSizeName.findMedia(mediaList, x, y,
550                                    Size2DSyntax.INCH);
551         } catch (IllegalArgumentException iae) {
552         }
553         if ((media == null) ||
554              !(service.isAttributeValueSupported(media, null, null))) {
555             media = (Media)service.getDefaultAttributeValue(Media.class);
556         }
557 
558         OrientationRequested orient;
559         switch (page.getOrientation()) {
560         case PageFormat.LANDSCAPE :
561             orient = OrientationRequested.LANDSCAPE;
562             break;
563         case PageFormat.REVERSE_LANDSCAPE:
564             orient = OrientationRequested.REVERSE_LANDSCAPE;
565             break;
566         default:
567             orient = OrientationRequested.PORTRAIT;
568         }
569 
570         if (attributes == null) {
571             attributes = new HashPrintRequestAttributeSet();
572         }
573         if (media != null) {
574             attributes.add(media);
575         }
576         attributes.add(orient);
577 
578         float ix = (float)(page.getPaper().getImageableX()/DPI);
579         float iw = (float)(page.getPaper().getImageableWidth()/DPI);
580         float iy = (float)(page.getPaper().getImageableY()/DPI);
581         float ih = (float)(page.getPaper().getImageableHeight()/DPI);
582         if (ix < 0) ix = 0f; if (iy < 0) iy = 0f;
583         try {
584             attributes.add(new MediaPrintableArea(ix, iy, iw, ih,
585                                                   MediaPrintableArea.INCH));
586         } catch (IllegalArgumentException iae) {
587         }
588     }
589 
590    /**
591      * Display a dialog to the user allowing the modification of a
592      * PageFormat instance.
593      * The <code>page</code> argument is used to initialize controls
594      * in the page setup dialog.
595      * If the user cancels the dialog, then the method returns the
596      * original <code>page</code> object unmodified.
597      * If the user okays the dialog then the method returns a new
598      * PageFormat object with the indicated changes.
599      * In either case the original <code>page</code> object will
600      * not be modified.
601      * @param     page    the default PageFormat presented to the user
602      *                    for modification
603      * @return    the original <code>page</code> object if the dialog
604      *            is cancelled, or a new PageFormat object containing
605      *            the format indicated by the user if the dialog is
606      *            acknowledged
607      * @exception HeadlessException if GraphicsEnvironment.isHeadless()
608      * returns true.
609      * @see java.awt.GraphicsEnvironment#isHeadless
610      * @since     1.2
611      */
612     public PageFormat pageDialog(PageFormat page)
613         throws HeadlessException {
614         if (GraphicsEnvironment.isHeadless()) {
615             throw new HeadlessException();
616         }
617 
618         final GraphicsConfiguration gc =
619           GraphicsEnvironment.getLocalGraphicsEnvironment().
620           getDefaultScreenDevice().getDefaultConfiguration();
621 
622         PrintService service =
623             (PrintService)java.security.AccessController.doPrivileged(
624                                         new java.security.PrivilegedAction() {
625                 public Object run() {
626                     PrintService service = getPrintService();
627                     if (service == null) {
628                         ServiceDialog.showNoPrintService(gc);
629                         return null;
630                     }
631                     return service;
632                 }
633             });
634 
635         if (service == null) {
636             return page;
637         }
638         updatePageAttributes(service, page);
639 
640         PageFormat newPage = pageDialog(attributes);
641 
642         if (newPage == null) {
643             return page;
644         } else {
645             return newPage;
646         }
647     }
648 
649     /**
650      * return a PageFormat corresponding to the updated attributes,
651      * or null if the user cancelled the dialog.
652      */
653     public PageFormat pageDialog(final PrintRequestAttributeSet attributes)
654         throws HeadlessException {
655         if (GraphicsEnvironment.isHeadless()) {
656             throw new HeadlessException();
657         }
658 
659         final GraphicsConfiguration gc =
660             GraphicsEnvironment.getLocalGraphicsEnvironment().
661             getDefaultScreenDevice().getDefaultConfiguration();
662         Rectangle bounds = gc.getBounds();
663         int x = bounds.x+bounds.width/3;
664         int y = bounds.y+bounds.height/3;
665 
666         PrintService service =
667             (PrintService)java.security.AccessController.doPrivileged(
668                                         new java.security.PrivilegedAction() {
669                 public Object run() {
670                     PrintService service = getPrintService();
671                     if (service == null) {
672                         ServiceDialog.showNoPrintService(gc);
673                         return null;
674                     }
675                     return service;
676                 }
677             });
678 
679         if (service == null) {
680             return null;
681         }
682 
683         ServiceDialog pageDialog = new ServiceDialog(gc, x, y, service,
684                                        DocFlavor.SERVICE_FORMATTED.PAGEABLE,
685                                        attributes, (Frame)null);
686         pageDialog.show();
687 
688         if (pageDialog.getStatus() == ServiceDialog.APPROVE) {
689             PrintRequestAttributeSet newas =
690                 pageDialog.getAttributes();
691             Class amCategory = SunAlternateMedia.class;
692 
693             if (attributes.containsKey(amCategory) &&
694                 !newas.containsKey(amCategory)) {
695                 attributes.remove(amCategory);
696             }
697             attributes.addAll(newas);
698 
699             PageFormat page = defaultPage();
700 
701             OrientationRequested orient =
702                 (OrientationRequested)
703                 attributes.get(OrientationRequested.class);
704             int pfOrient =  PageFormat.PORTRAIT;
705             if (orient != null) {
706                 if (orient == OrientationRequested.REVERSE_LANDSCAPE) {
707                     pfOrient = PageFormat.REVERSE_LANDSCAPE;
708                 } else if (orient == OrientationRequested.LANDSCAPE) {
709                     pfOrient = PageFormat.LANDSCAPE;
710                 }
711             }
712             page.setOrientation(pfOrient);
713 
714             Media media = (Media)attributes.get(Media.class);
715             if (media == null) {
716                 media =
717                     (Media)service.getDefaultAttributeValue(Media.class);
718             }
719             if (!(media instanceof MediaSizeName)) {
720                 media = MediaSizeName.NA_LETTER;
721             }
722             MediaSize size =
723                 MediaSize.getMediaSizeForName((MediaSizeName)media);
724             if (size == null) {
725                 size = MediaSize.NA.LETTER;
726             }
727             Paper paper = new Paper();
728             float dim[] = size.getSize(1); //units == 1 to avoid FP error
729             double w = Math.rint((dim[0]*72.0)/Size2DSyntax.INCH);
730             double h = Math.rint((dim[1]*72.0)/Size2DSyntax.INCH);
731             paper.setSize(w, h);
732             MediaPrintableArea area =
733                 (MediaPrintableArea)
734                 attributes.get(MediaPrintableArea.class);
735             double ix, iw, iy, ih;
736 
737             if (area != null) {
738                 // Should pass in same unit as updatePageAttributes
739                 // to avoid rounding off errors.
740                 ix = Math.rint(
741                                area.getX(MediaPrintableArea.INCH) * DPI);
742                 iy = Math.rint(
743                                area.getY(MediaPrintableArea.INCH) * DPI);
744                 iw = Math.rint(
745                                area.getWidth(MediaPrintableArea.INCH) * DPI);
746                 ih = Math.rint(
747                                area.getHeight(MediaPrintableArea.INCH) * DPI);
748             }
749             else {
750                 if (w >= 72.0 * 6.0) {
751                     ix = 72.0;
752                     iw = w - 2 * 72.0;
753                 } else {
754                     ix = w / 6.0;
755                     iw = w * 0.75;
756                 }
757                 if (h >= 72.0 * 6.0) {
758                     iy = 72.0;
759                     ih = h - 2 * 72.0;
760                 } else {
761                     iy = h / 6.0;
762                     ih = h * 0.75;
763                 }
764             }
765             paper.setImageableArea(ix, iy, iw, ih);
766             page.setPaper(paper);
767 
768             return page;
769         } else {
770             return null;
771         }
772    }
773 
774    /**
775      * Presents the user a dialog for changing properties of the
776      * print job interactively.
777      * The services browsable here are determined by the type of
778      * service currently installed.
779      * If the application installed a StreamPrintService on this
780      * PrinterJob, only the available StreamPrintService (factories) are
781      * browsable.
782      *
783      * @param attributes to store changed properties.
784      * @return false if the user cancels the dialog and true otherwise.
785      * @exception HeadlessException if GraphicsEnvironment.isHeadless()
786      * returns true.
787      * @see java.awt.GraphicsEnvironment#isHeadless
788      */
789     public boolean printDialog(final PrintRequestAttributeSet attributes)
790         throws HeadlessException {
791         if (GraphicsEnvironment.isHeadless()) {
792             throw new HeadlessException();
793         }
794 
795 
796         DialogTypeSelection dlg =
797             (DialogTypeSelection)attributes.get(DialogTypeSelection.class);
798 
799         // Check for native, note that default dialog is COMMON.
800         if (dlg == DialogTypeSelection.NATIVE) {
801             this.attributes = attributes;
802             try {
803                 debug_println("calling setAttributes in printDialog");
804                 setAttributes(attributes);
805 
806             } catch (PrinterException e) {
807 
808             }
809 
810             boolean ret = printDialog();
811             this.attributes = attributes;
812             return ret;
813 
814         }
815 
816 
817         /* A security check has already been performed in the
818          * java.awt.print.printerJob.getPrinterJob method.
819          * So by the time we get here, it is OK for the current thread
820          * to print either to a file (from a Dialog we control!) or
821          * to a chosen printer.
822          *
823          * We raise privilege when we put up the dialog, to avoid
824          * the "warning applet window" banner.
825          */
826         final GraphicsConfiguration gc =
827             GraphicsEnvironment.getLocalGraphicsEnvironment().
828             getDefaultScreenDevice().getDefaultConfiguration();
829 
830         PrintService service =
831             (PrintService)java.security.AccessController.doPrivileged(
832                        new java.security.PrivilegedAction() {
833                 public Object run() {
834                     PrintService service = getPrintService();
835                     if (service == null) {
836                         ServiceDialog.showNoPrintService(gc);
837                         return null;
838                     }
839                     return service;
840                 }
841             });
842 
843         if (service == null) {
844             return false;
845         }
846 
847         PrintService[] services;
848         StreamPrintServiceFactory[] spsFactories = null;
849         if (service instanceof StreamPrintService) {
850             spsFactories = lookupStreamPrintServices(null);
851             services = new StreamPrintService[spsFactories.length];
852             for (int i=0; i<spsFactories.length; i++) {
853                 services[i] = spsFactories[i].getPrintService(null);
854             }
855         } else {
856             services =
857             (PrintService[])java.security.AccessController.doPrivileged(
858                        new java.security.PrivilegedAction() {
859                 public Object run() {
860                     PrintService[] services = PrinterJob.lookupPrintServices();
861                     return services;
862                 }
863             });
864 
865             if ((services == null) || (services.length == 0)) {
866                 /*
867                  * No services but default PrintService exists?
868                  * Create services using defaultService.
869                  */
870                 services = new PrintService[1];
871                 services[0] = service;
872             }
873         }
874 
875         Rectangle bounds = gc.getBounds();
876         int x = bounds.x+bounds.width/3;
877         int y = bounds.y+bounds.height/3;
878         PrintService newService;
879         try {
880             newService =
881             ServiceUI.printDialog(gc, x, y,
882                                   services, service,
883                                   DocFlavor.SERVICE_FORMATTED.PAGEABLE,
884                                   attributes);
885         } catch (IllegalArgumentException iae) {
886             newService = ServiceUI.printDialog(gc, x, y,
887                                   services, services[0],
888                                   DocFlavor.SERVICE_FORMATTED.PAGEABLE,
889                                   attributes);
890         }
891 
892         if (newService == null) {
893             return false;
894         }
895 
896         if (!service.equals(newService)) {
897             try {
898                 setPrintService(newService);
899             } catch (PrinterException e) {
900                 /*
901                  * The only time it would throw an exception is when
902                  * newService is no longer available but we should still
903                  * select this printer.
904                  */
905                 myService = newService;
906             }
907         }
908         return true;
909     }
910 
911    /**
912      * Presents the user a dialog for changing properties of the
913      * print job interactively.
914      * @returns false if the user cancels the dialog and
915      *          true otherwise.
916      * @exception HeadlessException if GraphicsEnvironment.isHeadless()
917      * returns true.
918      * @see java.awt.GraphicsEnvironment#isHeadless
919      */
920     public boolean printDialog() throws HeadlessException {
921 
922         if (GraphicsEnvironment.isHeadless()) {
923             throw new HeadlessException();
924         }
925 
926         PrintRequestAttributeSet attributes =
927           new HashPrintRequestAttributeSet();
928         attributes.add(new Copies(getCopies()));
929         attributes.add(new JobName(getJobName(), null));
930         boolean doPrint = printDialog(attributes);
931         if (doPrint) {
932             JobName jobName = (JobName)attributes.get(JobName.class);
933             if (jobName != null) {
934                 setJobName(jobName.getValue());
935             }
936             Copies copies = (Copies)attributes.get(Copies.class);
937             if (copies != null) {
938                 setCopies(copies.getValue());
939             }
940 
941             Destination dest = (Destination)attributes.get(Destination.class);
942 
943             if (dest != null) {
944                 try {
945                     mDestType = RasterPrinterJob.FILE;
946                     mDestination = (new File(dest.getURI())).getPath();
947                 } catch (Exception e) {
948                     mDestination = "out.prn";
949                     PrintService ps = getPrintService();
950                     if (ps != null) {
951                         Destination defaultDest = (Destination)ps.
952                             getDefaultAttributeValue(Destination.class);
953                         if (defaultDest != null) {
954                             mDestination = (new File(defaultDest.getURI())).getPath();
955                         }
956                     }
957                 }
958             } else {
959                 mDestType = RasterPrinterJob.PRINTER;
960                 PrintService ps = getPrintService();
961                 if (ps != null) {
962                     mDestination = ps.getName();
963                 }
964             }
965         }
966 
967         return doPrint;
968     }
969 
970     /**
971      * The pages in the document to be printed by this PrinterJob
972      * are drawn by the Printable object 'painter'. The PageFormat
973      * for each page is the default page format.
974      * @param Printable Called to render each page of the document.
975      */
976     public void setPrintable(Printable painter) {
977         setPageable(new OpenBook(defaultPage(new PageFormat()), painter));
978     }
979 
980     /**
981      * The pages in the document to be printed by this PrinterJob
982      * are drawn by the Printable object 'painter'. The PageFormat
983      * of each page is 'format'.
984      * @param Printable Called to render each page of the document.
985      * @param PageFormat The size and orientation of each page to
986      *                   be printed.
987      */
988     public void setPrintable(Printable painter, PageFormat format) {
989         setPageable(new OpenBook(format, painter));
990         updatePageAttributes(getPrintService(), format);
991     }
992 
993     /**
994      * The pages in the document to be printed are held by the
995      * Pageable instance 'document'. 'document' will be queried
996      * for the number of pages as well as the PageFormat and
997      * Printable for each page.
998      * @param Pageable The document to be printed. It may not be null.
999      * @exception NullPointerException the Pageable passed in was null.
1000      * @see PageFormat
1001      * @see Printable
1002      */
1003     public void setPageable(Pageable document) throws NullPointerException {
1004         if (document != null) {
1005             mDocument = document;
1006 
1007         } else {
1008             throw new NullPointerException();
1009         }
1010     }
1011 
1012     protected void initPrinter() {
1013         return;
1014     }
1015 
1016     protected boolean isSupportedValue(Attribute attrval,
1017                                      PrintRequestAttributeSet attrset) {
1018         PrintService ps = getPrintService();
1019         return
1020             (attrval != null && ps != null &&
1021              ps.isAttributeValueSupported(attrval,
1022                                           DocFlavor.SERVICE_FORMATTED.PAGEABLE,
1023                                           attrset));
1024     }
1025 
1026     /* subclasses may need to pull extra information out of the attribute set
1027      * They can override this method & call super.setAttributes()
1028      */
1029     protected  void setAttributes(PrintRequestAttributeSet attributes)
1030         throws PrinterException {
1031         /*  reset all values to defaults */
1032         setCollated(false);
1033         sidesAttr = null;
1034         pageRangesAttr = null;
1035         copiesAttr = 0;
1036         jobNameAttr = null;
1037         userNameAttr = null;
1038         destinationAttr = null;
1039         collateAttReq = false;
1040 
1041         PrintService service = getPrintService();
1042         if (attributes == null  || service == null) {
1043             return;
1044         }
1045 
1046         boolean fidelity = false;
1047         Fidelity attrFidelity = (Fidelity)attributes.get(Fidelity.class);
1048         if (attrFidelity != null && attrFidelity == Fidelity.FIDELITY_TRUE) {
1049             fidelity = true;
1050         }
1051 
1052         if (fidelity == true) {
1053            AttributeSet unsupported =
1054                service.getUnsupportedAttributes(
1055                                          DocFlavor.SERVICE_FORMATTED.PAGEABLE,
1056                                          attributes);
1057            if (unsupported != null) {
1058                throw new PrinterException("Fidelity cannot be satisfied");
1059            }
1060         }
1061 
1062         /*
1063          * Since we have verified supported values if fidelity is true,
1064          * we can either ignore unsupported values, or substitute a
1065          * reasonable alternative
1066          */
1067 
1068         SheetCollate collateAttr =
1069             (SheetCollate)attributes.get(SheetCollate.class);
1070         if (isSupportedValue(collateAttr,  attributes)) {
1071             setCollated(collateAttr == SheetCollate.COLLATED);
1072         }
1073 
1074         sidesAttr = (Sides)attributes.get(Sides.class);
1075         if (!isSupportedValue(sidesAttr,  attributes)) {
1076             sidesAttr = Sides.ONE_SIDED;
1077         }
1078 
1079         pageRangesAttr =  (PageRanges)attributes.get(PageRanges.class);
1080         if (!isSupportedValue(pageRangesAttr, attributes)) {
1081             pageRangesAttr = null;
1082         } else {
1083             if ((SunPageSelection)attributes.get(SunPageSelection.class)
1084                      == SunPageSelection.RANGE) {
1085                 // get to, from, min, max page ranges
1086                 int[][] range = pageRangesAttr.getMembers();
1087                 // setPageRanges uses 0-based indexing so we subtract 1
1088                 setPageRange(range[0][0] - 1, range[0][1] - 1);
1089             } else {
1090                setPageRange(-1, - 1);
1091             }
1092         }
1093 
1094         Copies copies = (Copies)attributes.get(Copies.class);
1095         if (isSupportedValue(copies,  attributes) ||
1096             (!fidelity && copies != null)) {
1097             copiesAttr = copies.getValue();
1098             setCopies(copiesAttr);
1099         } else {
1100             copiesAttr = getCopies();
1101         }
1102 
1103         Destination destination =
1104             (Destination)attributes.get(Destination.class);
1105 
1106         if (isSupportedValue(destination,  attributes)) {
1107             try {
1108                 // Old code (new File(destination.getURI())).getPath()
1109                 // would generate a "URI is not hierarchical" IAE
1110                 // for "file:out.prn" so we use getSchemeSpecificPart instead
1111                 destinationAttr = "" + new File(destination.getURI().
1112                                                 getSchemeSpecificPart());
1113             } catch (Exception e) { // paranoid exception
1114                 Destination defaultDest = (Destination)service.
1115                     getDefaultAttributeValue(Destination.class);
1116                 if (defaultDest != null) {
1117                     destinationAttr = "" + new File(defaultDest.getURI().
1118                                                 getSchemeSpecificPart());
1119                 }
1120             }
1121         }
1122 
1123         JobSheets jobSheets = (JobSheets)attributes.get(JobSheets.class);
1124         if (jobSheets != null) {
1125             noJobSheet = jobSheets == JobSheets.NONE;
1126         }
1127 
1128         JobName jobName = (JobName)attributes.get(JobName.class);
1129         if (isSupportedValue(jobName,  attributes) ||
1130             (!fidelity && jobName != null)) {
1131             jobNameAttr = jobName.getValue();
1132             setJobName(jobNameAttr);
1133         } else {
1134             jobNameAttr = getJobName();
1135         }
1136 
1137         RequestingUserName userName =
1138             (RequestingUserName)attributes.get(RequestingUserName.class);
1139         if (isSupportedValue(userName,  attributes) ||
1140             (!fidelity && userName != null)) {
1141             userNameAttr = userName.getValue();
1142         } else {
1143             try {
1144                 userNameAttr = getUserName();
1145             } catch (SecurityException e) {
1146                 userNameAttr = "";
1147             }
1148         }
1149 
1150         /* OpenBook is used internally only when app uses Printable.
1151          * This is the case when we use the values from the attribute set.
1152          */
1153         Media media = (Media)attributes.get(Media.class);
1154         OrientationRequested orientReq =
1155            (OrientationRequested)attributes.get(OrientationRequested.class);
1156         MediaPrintableArea mpa =
1157             (MediaPrintableArea)attributes.get(MediaPrintableArea.class);
1158 
1159         if ((orientReq != null || media != null || mpa != null) &&
1160             getPageable() instanceof OpenBook) {
1161 
1162             /* We could almost(!) use PrinterJob.getPageFormat() except
1163              * here we need to start with the PageFormat from the OpenBook :
1164              */
1165             Pageable pageable = getPageable();
1166             Printable printable = pageable.getPrintable(0);
1167             PageFormat pf = (PageFormat)pageable.getPageFormat(0).clone();
1168             Paper paper = pf.getPaper();
1169 
1170             /* If there's a media but no media printable area, we can try
1171              * to retrieve the default value for mpa and use that.
1172              */
1173             if (mpa == null && media != null &&
1174                 service.
1175                 isAttributeCategorySupported(MediaPrintableArea.class)) {
1176                 Object mpaVals = service.
1177                     getSupportedAttributeValues(MediaPrintableArea.class,
1178                                                 null, attributes);
1179                 if (mpaVals instanceof MediaPrintableArea[] &&
1180                     ((MediaPrintableArea[])mpaVals).length > 0) {
1181                     mpa = ((MediaPrintableArea[])mpaVals)[0];
1182                 }
1183             }
1184 
1185             if (isSupportedValue(orientReq, attributes) ||
1186                 (!fidelity && orientReq != null)) {
1187                 int orient;
1188                 if (orientReq.equals(OrientationRequested.REVERSE_LANDSCAPE)) {
1189                     orient = PageFormat.REVERSE_LANDSCAPE;
1190                 } else if (orientReq.equals(OrientationRequested.LANDSCAPE)) {
1191                     orient = PageFormat.LANDSCAPE;
1192                 } else {
1193                     orient = PageFormat.PORTRAIT;
1194                 }
1195                 pf.setOrientation(orient);
1196             }
1197 
1198             if (isSupportedValue(media, attributes) ||
1199                 (!fidelity && media != null)) {
1200                 if (media instanceof MediaSizeName) {
1201                     MediaSizeName msn = (MediaSizeName)media;
1202                     MediaSize msz = MediaSize.getMediaSizeForName(msn);
1203                     if (msz != null) {
1204                         float paperWid =  msz.getX(MediaSize.INCH) * 72.0f;
1205                         float paperHgt =  msz.getY(MediaSize.INCH) * 72.0f;
1206                         paper.setSize(paperWid, paperHgt);
1207                         if (mpa == null) {
1208                             paper.setImageableArea(72.0, 72.0,
1209                                                    paperWid-144.0,
1210                                                    paperHgt-144.0);
1211                         }
1212                     }
1213                 }
1214             }
1215 
1216             if (isSupportedValue(mpa, attributes) ||
1217                 (!fidelity && mpa != null)) {
1218                 float [] printableArea =
1219                     mpa.getPrintableArea(MediaPrintableArea.INCH);
1220                 for (int i=0; i < printableArea.length; i++) {
1221                     printableArea[i] = printableArea[i]*72.0f;
1222                 }
1223                 paper.setImageableArea(printableArea[0], printableArea[1],
1224                                        printableArea[2], printableArea[3]);
1225             }
1226 
1227             pf.setPaper(paper);
1228             pf = validatePage(pf);
1229             setPrintable(printable, pf);
1230         } else {
1231             // for AWT where pageable is not an instance of OpenBook,
1232             // we need to save paper info
1233             this.attributes = attributes;
1234         }
1235 
1236     }
1237 
1238     /*
1239      * Services we don't recognize as built-in services can't be
1240      * implemented as subclasses of PrinterJob, therefore we create
1241      * a DocPrintJob from their service and pass a Doc representing
1242      * the application's printjob
1243      */
1244     private void spoolToService(PrintService psvc,
1245                                 PrintRequestAttributeSet attributes)
1246         throws PrinterException {
1247 
1248         if (psvc == null) {
1249             throw new PrinterException("No print service found.");
1250         }
1251 
1252         DocPrintJob job = psvc.createPrintJob();
1253         Doc doc = new PageableDoc(getPageable());
1254         if (attributes == null) {
1255             attributes = new HashPrintRequestAttributeSet();
1256         }
1257         try {
1258             job.print(doc, attributes);
1259         } catch (PrintException e) {
1260             throw new PrinterException(e.toString());
1261         }
1262     }
1263 
1264     /**
1265      * Prints a set of pages.
1266      * @exception java.awt.print.PrinterException an error in the print system
1267      *                                          caused the job to be aborted
1268      * @see java.awt.print.Book
1269      * @see java.awt.print.Pageable
1270      * @see java.awt.print.Printable
1271      */
1272     public void print() throws PrinterException {
1273         print(attributes);
1274     }
1275 
1276     public static boolean debugPrint = false;
1277     protected void debug_println(String str) {
1278         if (debugPrint) {
1279             System.out.println("RasterPrinterJob "+str+" "+this);
1280         }
1281     }
1282 
1283     public void print(PrintRequestAttributeSet attributes)
1284         throws PrinterException {
1285 
1286         /*
1287          * In the future PrinterJob will probably always dispatch
1288          * the print job to the PrintService.
1289          * This is how third party 2D Print Services will be invoked
1290          * when applications use the PrinterJob API.
1291          * However the JRE's concrete PrinterJob implementations have
1292          * not yet been re-worked to be implemented as standalone
1293          * services, and are implemented only as subclasses of PrinterJob.
1294          * So here we dispatch only those services we do not recognize
1295          * as implemented through platform subclasses of PrinterJob
1296          * (and this class).
1297          */
1298         PrintService psvc = getPrintService();
1299         debug_println("psvc = "+psvc);
1300         if (psvc == null) {
1301             throw new PrinterException("No print service found.");
1302         }
1303 
1304         // Check the list of services.  This service may have been
1305         // deleted already
1306         PrinterState prnState = (PrinterState)psvc.getAttribute(
1307                                                   PrinterState.class);
1308         if (prnState == PrinterState.STOPPED) {
1309             PrinterStateReasons prnStateReasons =
1310                     (PrinterStateReasons)psvc.getAttribute(
1311                                                  PrinterStateReasons.class);
1312                 if ((prnStateReasons != null) &&
1313                     (prnStateReasons.containsKey(PrinterStateReason.SHUTDOWN)))
1314                 {
1315                     throw new PrinterException("PrintService is no longer available.");
1316                 }
1317         }
1318 
1319         if ((PrinterIsAcceptingJobs)(psvc.getAttribute(
1320                          PrinterIsAcceptingJobs.class)) ==
1321                          PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS) {
1322             throw new PrinterException("Printer is not accepting job.");
1323         }
1324 
1325         if ((psvc instanceof SunPrinterJobService) &&
1326             ((SunPrinterJobService)psvc).usesClass(getClass())) {
1327             setAttributes(attributes);
1328             // throw exception for invalid destination
1329             if (destinationAttr != null) {
1330                 // destinationAttr is null for Destination(new URI(""))
1331                 // because isAttributeValueSupported returns false in setAttributes
1332 
1333                 // Destination(new URI(" ")) throws URISyntaxException
1334                 File f = new File(destinationAttr);
1335                 try {
1336                     // check if this is a new file and if filename chars are valid
1337                     if (f.createNewFile()) {
1338                         f.delete();
1339                     }
1340                 } catch (IOException ioe) {
1341                     throw new PrinterException("Cannot write to file:"+
1342                                                destinationAttr);
1343                 } catch (SecurityException se) {
1344                     //There is already file read/write access so at this point
1345                     // only delete access is denied.  Just ignore it because in
1346                     // most cases the file created in createNewFile gets overwritten
1347                     // anyway.
1348                 }
1349 
1350                 File pFile = f.getParentFile();
1351                 if ((f.exists() &&
1352                      (!f.isFile() || !f.canWrite())) ||
1353                     ((pFile != null) &&
1354                      (!pFile.exists() || (pFile.exists() && !pFile.canWrite())))) {
1355                     throw new PrinterException("Cannot write to file:"+
1356                                                destinationAttr);
1357                 }
1358             }
1359         } else {
1360             spoolToService(psvc, attributes);
1361             return;
1362         }
1363         /* We need to make sure that the collation and copies
1364          * settings are initialised */
1365         initPrinter();
1366 
1367         int numCollatedCopies = getCollatedCopies();
1368         int numNonCollatedCopies = getNoncollatedCopies();
1369         debug_println("getCollatedCopies()  "+numCollatedCopies
1370               + " getNoncollatedCopies() "+ numNonCollatedCopies);
1371 
1372         /* Get the range of pages we are to print. If the
1373          * last page to print is unknown, then we print to
1374          * the end of the document. Note that firstPage
1375          * and lastPage are 0 based page indices.
1376          */
1377         int numPages = mDocument.getNumberOfPages();
1378         if (numPages == 0) {
1379             return;
1380         }
1381 
1382         int firstPage = getFirstPage();
1383         int lastPage = getLastPage();
1384         if(lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES){
1385             int totalPages = mDocument.getNumberOfPages();
1386             if (totalPages != Pageable.UNKNOWN_NUMBER_OF_PAGES) {
1387                 lastPage = mDocument.getNumberOfPages() - 1;
1388             }
1389         }
1390 
1391         try {
1392             synchronized (this) {
1393                 performingPrinting = true;
1394                 userCancelled = false;
1395             }
1396 
1397             startDoc();
1398             if (isCancelled()) {
1399                 cancelDoc();
1400             }
1401 
1402             // PageRanges can be set even if RANGE is not selected
1403             // so we need to check if it is selected.
1404             boolean rangeIsSelected = true;
1405             if (attributes != null) {
1406                 SunPageSelection pages =
1407                     (SunPageSelection)attributes.get(SunPageSelection.class);
1408                 if ((pages != null) && (pages != SunPageSelection.RANGE)) {
1409                     rangeIsSelected = false;
1410                 }
1411             }
1412 
1413 
1414             debug_println("after startDoc rangeSelected? "+rangeIsSelected
1415                       + " numNonCollatedCopies "+ numNonCollatedCopies);
1416 
1417 
1418             /* Three nested loops iterate over the document. The outer loop
1419              * counts the number of collated copies while the inner loop
1420              * counts the number of nonCollated copies. Normally, one of
1421              * these two loops will only execute once; that is we will
1422              * either print collated copies or noncollated copies. The
1423              * middle loop iterates over the pages.
1424              * If a PageRanges attribute is used, it constrains the pages
1425              * that are imaged. If a platform subclass (though a user dialog)
1426              * requests a page range via setPageRange(). it too can
1427              * constrain the page ranges that are imaged.
1428              * It is expected that only one of these will be used in a
1429              * job but both should be able to co-exist.
1430              */
1431             for(int collated = 0; collated < numCollatedCopies; collated++) {
1432                 for(int i = firstPage, pageResult = Printable.PAGE_EXISTS;
1433                     (i <= lastPage ||
1434                      lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES)
1435                     && pageResult == Printable.PAGE_EXISTS;
1436                     i++)
1437                 {
1438 
1439                     if ((pageRangesAttr != null) && rangeIsSelected ){
1440                         int nexti = pageRangesAttr.next(i);
1441                         if (nexti == -1) {
1442                             break;
1443                         } else if (nexti != i+1) {
1444                             continue;
1445                         }
1446                     }
1447 
1448                     for(int nonCollated = 0;
1449                         nonCollated < numNonCollatedCopies
1450                         && pageResult == Printable.PAGE_EXISTS;
1451                         nonCollated++)
1452                     {
1453                         if (isCancelled()) {
1454                             cancelDoc();
1455                         }
1456                         debug_println("printPage "+i);
1457                         pageResult = printPage(mDocument, i);
1458 
1459                     }
1460                 }
1461             }
1462 
1463             if (isCancelled()) {
1464                 cancelDoc();
1465             }
1466 
1467         } finally {
1468             // reset previousPaper in case this job is invoked again.
1469             previousPaper = null;
1470             synchronized (this) {
1471                 if (performingPrinting) {
1472                     endDoc();
1473                 }
1474                 performingPrinting = false;
1475                 notify();
1476             }
1477         }
1478     }
1479 
1480     /**
1481      * updates a Paper object to reflect the current printer's selected
1482      * paper size and imageable area for that paper size.
1483      * Default implementation copies settings from the original, applies
1484      * applies some validity checks, changes them only if they are
1485      * clearly unreasonable, then sets them into the new Paper.
1486      * Subclasses are expected to override this method to make more
1487      * informed decisons.
1488      */
1489     protected void validatePaper(Paper origPaper, Paper newPaper) {
1490         if (origPaper == null || newPaper == null) {
1491             return;
1492         } else {
1493             double wid = origPaper.getWidth();
1494             double hgt = origPaper.getHeight();
1495             double ix = origPaper.getImageableX();
1496             double iy = origPaper.getImageableY();
1497             double iw = origPaper.getImageableWidth();
1498             double ih = origPaper.getImageableHeight();
1499 
1500             /* Assume any +ve values are legal. Overall paper dimensions
1501              * take precedence. Make sure imageable area fits on the paper.
1502              */
1503             Paper defaultPaper = new Paper();
1504             wid = ((wid > 0.0) ? wid : defaultPaper.getWidth());
1505             hgt = ((hgt > 0.0) ? hgt : defaultPaper.getHeight());
1506             ix = ((ix > 0.0) ? ix : defaultPaper.getImageableX());
1507             iy = ((iy > 0.0) ? iy : defaultPaper.getImageableY());
1508             iw = ((iw > 0.0) ? iw : defaultPaper.getImageableWidth());
1509             ih = ((ih > 0.0) ? ih : defaultPaper.getImageableHeight());
1510             /* full width/height is not likely to be imageable, but since we
1511              * don't know the limits we have to allow it
1512              */
1513             if (iw > wid) {
1514                 iw = wid;
1515             }
1516             if (ih > hgt) {
1517                 ih = hgt;
1518             }
1519             if ((ix + iw) > wid) {
1520                 ix = wid - iw;
1521             }
1522             if ((iy + ih) > hgt) {
1523                 iy = hgt - ih;
1524             }
1525             newPaper.setSize(wid, hgt);
1526             newPaper.setImageableArea(ix, iy, iw, ih);
1527         }
1528     }
1529 
1530     /**
1531      * The passed in PageFormat will be copied and altered to describe
1532      * the default page size and orientation of the PrinterJob's
1533      * current printer.
1534      * Platform subclasses which can access the actual default paper size
1535      * for a printer may override this method.
1536      */
1537     public PageFormat defaultPage(PageFormat page) {
1538         PageFormat newPage = (PageFormat)page.clone();
1539         newPage.setOrientation(PageFormat.PORTRAIT);
1540         Paper newPaper = new Paper();
1541         double ptsPerInch = 72.0;
1542         double w, h;
1543         Media media = null;
1544 
1545         PrintService service = getPrintService();
1546         if (service != null) {
1547             MediaSize size;
1548             media =
1549                 (Media)service.getDefaultAttributeValue(Media.class);
1550 
1551             if (media instanceof MediaSizeName &&
1552                ((size = MediaSize.getMediaSizeForName((MediaSizeName)media)) !=
1553                 null)) {
1554                 w =  size.getX(MediaSize.INCH) * ptsPerInch;
1555                 h =  size.getY(MediaSize.INCH) * ptsPerInch;
1556                 newPaper.setSize(w, h);
1557                 newPaper.setImageableArea(ptsPerInch, ptsPerInch,
1558                                           w - 2.0*ptsPerInch,
1559                                           h - 2.0*ptsPerInch);
1560                 newPage.setPaper(newPaper);
1561                 return newPage;
1562 
1563             }
1564         }
1565 
1566         /* Default to A4 paper outside North America.
1567          */
1568         String defaultCountry = Locale.getDefault().getCountry();
1569         if (!Locale.getDefault().equals(Locale.ENGLISH) && // ie "C"
1570             defaultCountry != null &&
1571             !defaultCountry.equals(Locale.US.getCountry()) &&
1572             !defaultCountry.equals(Locale.CANADA.getCountry())) {
1573 
1574             double mmPerInch = 25.4;
1575             w = Math.rint((210.0*ptsPerInch)/mmPerInch);
1576             h = Math.rint((297.0*ptsPerInch)/mmPerInch);
1577             newPaper.setSize(w, h);
1578             newPaper.setImageableArea(ptsPerInch, ptsPerInch,
1579                                       w - 2.0*ptsPerInch,
1580                                       h - 2.0*ptsPerInch);
1581         }
1582 
1583         newPage.setPaper(newPaper);
1584 
1585         return newPage;
1586     }
1587 
1588     /**
1589      * The passed in PageFormat is cloned and altered to be usable on
1590      * the PrinterJob's current printer.
1591      */
1592     public PageFormat validatePage(PageFormat page) {
1593         PageFormat newPage = (PageFormat)page.clone();
1594         Paper newPaper = new Paper();
1595         validatePaper(newPage.getPaper(), newPaper);
1596         newPage.setPaper(newPaper);
1597 
1598         return newPage;
1599     }
1600 
1601     /**
1602      * Set the number of copies to be printed.
1603      */
1604     public void setCopies(int copies) {
1605         mNumCopies = copies;
1606     }
1607 
1608     /**
1609      * Get the number of copies to be printed.
1610      */
1611     public int getCopies() {
1612         return mNumCopies;
1613     }
1614 
1615    /* Used when executing a print job where an attribute set may
1616      * over ride API values.
1617      */
1618     protected int getCopiesInt() {
1619         return (copiesAttr > 0) ? copiesAttr : getCopies();
1620     }
1621 
1622     /**
1623      * Get the name of the printing user.
1624      * The caller must have security permission to read system properties.
1625      */
1626     public String getUserName() {
1627         return System.getProperty("user.name");
1628     }
1629 
1630    /* Used when executing a print job where an attribute set may
1631      * over ride API values.
1632      */
1633     protected String getUserNameInt() {
1634         if  (userNameAttr != null) {
1635             return userNameAttr;
1636         } else {
1637             try {
1638                 return  getUserName();
1639             } catch (SecurityException e) {
1640                 return "";
1641             }
1642         }
1643     }
1644 
1645     /**
1646      * Set the name of the document to be printed.
1647      * The document name can not be null.
1648      */
1649     public void setJobName(String jobName) {
1650         if (jobName != null) {
1651             mDocName = jobName;
1652         } else {
1653             throw new NullPointerException();
1654         }
1655     }
1656 
1657     /**
1658      * Get the name of the document to be printed.
1659      */
1660     public String getJobName() {
1661         return mDocName;
1662     }
1663 
1664     /* Used when executing a print job where an attribute set may
1665      * over ride API values.
1666      */
1667     protected String getJobNameInt() {
1668         return (jobNameAttr != null) ? jobNameAttr : getJobName();
1669     }
1670 
1671     /**
1672      * Set the range of pages from a Book to be printed.
1673      * Both 'firstPage' and 'lastPage' are zero based
1674      * page indices. If either parameter is less than
1675      * zero then the page range is set to be from the
1676      * first page to the last.
1677      */
1678     protected void setPageRange(int firstPage, int lastPage) {
1679         if(firstPage >= 0 && lastPage >= 0) {
1680             mFirstPage = firstPage;
1681             mLastPage = lastPage;
1682             if(mLastPage < mFirstPage) mLastPage = mFirstPage;
1683         } else {
1684             mFirstPage = Pageable.UNKNOWN_NUMBER_OF_PAGES;
1685             mLastPage = Pageable.UNKNOWN_NUMBER_OF_PAGES;
1686         }
1687     }
1688 
1689     /**
1690      * Return the zero based index of the first page to
1691      * be printed in this job.
1692      */
1693     protected int getFirstPage() {
1694         return mFirstPage == Book.UNKNOWN_NUMBER_OF_PAGES ? 0 : mFirstPage;
1695     }
1696 
1697     /**
1698      * Return the zero based index of the last page to
1699      * be printed in this job.
1700      */
1701     protected int getLastPage() {
1702         return mLastPage;
1703     }
1704 
1705     /**
1706      * Set whether copies should be collated or not.
1707      * Two collated copies of a three page document
1708      * print in this order: 1, 2, 3, 1, 2, 3 while
1709      * uncollated copies print in this order:
1710      * 1, 1, 2, 2, 3, 3.
1711      * This is set when request is using an attribute set.
1712      */
1713     protected void setCollated(boolean collate) {
1714         mCollate = collate;
1715         collateAttReq = true;
1716     }
1717 
1718     /**
1719      * Return true if collated copies will be printed as determined
1720      * in an attribute set.
1721      */
1722     protected boolean isCollated() {
1723             return mCollate;
1724     }
1725 
1726     /**
1727      * Called by the print() method at the start of
1728      * a print job.
1729      */
1730     protected abstract void startDoc() throws PrinterException;
1731 
1732     /**
1733      * Called by the print() method at the end of
1734      * a print job.
1735      */
1736     protected abstract void endDoc() throws PrinterException;
1737 
1738     /* Called by cancelDoc */
1739     protected abstract void abortDoc();
1740 
1741     private void cancelDoc() throws PrinterAbortException {
1742         abortDoc();
1743         synchronized (this) {
1744             userCancelled = false;
1745             performingPrinting = false;
1746             notify();
1747         }
1748         throw new PrinterAbortException();
1749     }
1750 
1751     /**
1752      * Returns how many times the entire book should
1753      * be printed by the PrintJob. If the printer
1754      * itself supports collation then this method
1755      * should return 1 indicating that the entire
1756      * book need only be printed once and the copies
1757      * will be collated and made in the printer.
1758      */
1759     protected int getCollatedCopies() {
1760         return isCollated() ? getCopiesInt() : 1;
1761     }
1762 
1763     /**
1764      * Returns how many times each page in the book
1765      * should be consecutively printed by PrintJob.
1766      * If the printer makes copies itself then this
1767      * method should return 1.
1768      */
1769     protected int getNoncollatedCopies() {
1770         return isCollated() ? 1 : getCopiesInt();
1771     }
1772 
1773 
1774     /* The printer graphics config is cached on the job, so that it can
1775      * be created once, and updated only as needed (for now only to change
1776      * the bounds if when using a Pageable the page sizes changes).
1777      */
1778 
1779     private int deviceWidth, deviceHeight;
1780     private AffineTransform defaultDeviceTransform;
1781     private PrinterGraphicsConfig pgConfig;
1782 
1783     synchronized void setGraphicsConfigInfo(AffineTransform at,
1784                                             double pw, double ph) {
1785         Point2D.Double pt = new Point2D.Double(pw, ph);
1786         at.transform(pt, pt);
1787 
1788         if (pgConfig == null ||
1789             defaultDeviceTransform == null ||
1790             !at.equals(defaultDeviceTransform) ||
1791             deviceWidth != (int)pt.getX() ||
1792             deviceHeight != (int)pt.getY()) {
1793 
1794                 deviceWidth = (int)pt.getX();
1795                 deviceHeight = (int)pt.getY();
1796                 defaultDeviceTransform = at;
1797                 pgConfig = null;
1798         }
1799     }
1800 
1801     synchronized PrinterGraphicsConfig getPrinterGraphicsConfig() {
1802         if (pgConfig != null) {
1803             return pgConfig;
1804         }
1805         String deviceID = "Printer Device";
1806         PrintService service = getPrintService();
1807         if (service != null) {
1808             deviceID = service.toString();
1809         }
1810         pgConfig = new PrinterGraphicsConfig(deviceID,
1811                                              defaultDeviceTransform,
1812                                              deviceWidth, deviceHeight);
1813         return pgConfig;
1814     }
1815 
1816     /**
1817      * Print a page from the provided document.
1818      * @return int Printable.PAGE_EXISTS if the page existed and was drawn and
1819      *             Printable.NO_SUCH_PAGE if the page did not exist.
1820      * @see java.awt.print.Printable
1821      */
1822     protected int printPage(Pageable document, int pageIndex)
1823         throws PrinterException
1824     {
1825         PageFormat page;
1826         PageFormat origPage;
1827         Printable painter;
1828         try {
1829             origPage = document.getPageFormat(pageIndex);
1830             page = (PageFormat)origPage.clone();
1831             painter = document.getPrintable(pageIndex);
1832         } catch (Exception e) {
1833             PrinterException pe =
1834                     new PrinterException("Error getting page or printable.[ " +
1835                                           e +" ]");
1836             pe.initCause(e);
1837             throw pe;
1838         }
1839 
1840         /* Get the imageable area from Paper instead of PageFormat
1841          * because we do not want it adjusted by the page orientation.
1842          */
1843         Paper paper = page.getPaper();
1844         // if non-portrait and 270 degree landscape rotation
1845         if (page.getOrientation() != PageFormat.PORTRAIT &&
1846             landscapeRotates270) {
1847 
1848             double left = paper.getImageableX();
1849             double top = paper.getImageableY();
1850             double width = paper.getImageableWidth();
1851             double height = paper.getImageableHeight();
1852             paper.setImageableArea(paper.getWidth()-left-width,
1853                                    paper.getHeight()-top-height,
1854                                    width, height);
1855             page.setPaper(paper);
1856             if (page.getOrientation() == PageFormat.LANDSCAPE) {
1857                 page.setOrientation(PageFormat.REVERSE_LANDSCAPE);
1858             } else {
1859                 page.setOrientation(PageFormat.LANDSCAPE);
1860             }
1861         }
1862 
1863         double xScale = getXRes() / 72.0;
1864         double yScale = getYRes() / 72.0;
1865 
1866         /* The deviceArea is the imageable area in the printer's
1867          * resolution.
1868          */
1869         Rectangle2D deviceArea =
1870             new Rectangle2D.Double(paper.getImageableX() * xScale,
1871                                    paper.getImageableY() * yScale,
1872                                    paper.getImageableWidth() * xScale,
1873                                    paper.getImageableHeight() * yScale);
1874 
1875         /* Build and hold on to a uniform transform so that
1876          * we can get back to device space at the beginning
1877          * of each band.
1878          */
1879         AffineTransform uniformTransform = new AffineTransform();
1880 
1881         /* The scale transform is used to switch from the
1882          * device space to the user's 72 dpi space.
1883          */
1884         AffineTransform scaleTransform = new AffineTransform();
1885         scaleTransform.scale(xScale, yScale);
1886 
1887         /* bandwidth is multiple of 4 as the data is used in a win32 DIB and
1888          * some drivers behave badly if scanlines aren't multiples of 4 bytes.
1889          */
1890         int bandWidth = (int) deviceArea.getWidth();
1891         if (bandWidth % 4 != 0) {
1892             bandWidth += (4 - (bandWidth % 4));
1893         }
1894         if (bandWidth <= 0) {
1895             throw new PrinterException("Paper's imageable width is too small.");
1896         }
1897 
1898         int deviceAreaHeight = (int)deviceArea.getHeight();
1899         if (deviceAreaHeight <= 0) {
1900             throw new PrinterException("Paper's imageable height is too small.");
1901         }
1902 
1903         /* Figure out the number of lines that will fit into
1904          * our maximum band size. The hard coded 3 reflects the
1905          * fact that we can only create 24 bit per pixel 3 byte BGR
1906          * BufferedImages. FIX.
1907          */
1908         int bandHeight = (int)(MAX_BAND_SIZE / bandWidth / 3);
1909 
1910         int deviceLeft = (int)Math.rint(paper.getImageableX() * xScale);
1911         int deviceTop  = (int)Math.rint(paper.getImageableY() * yScale);
1912 
1913         /* The device transform is used to move the band down
1914          * the page using translates. Normally this is all it
1915          * would do, but since, when printing, the Window's
1916          * DIB format wants the last line to be first (lowest) in
1917          * memory, the deviceTransform moves the origin to the
1918          * bottom of the band and flips the origin. This way the
1919          * app prints upside down into the band which is the DIB
1920          * format.
1921          */
1922         AffineTransform deviceTransform = new AffineTransform();
1923         deviceTransform.translate(-deviceLeft, deviceTop);
1924         deviceTransform.translate(0, bandHeight);
1925         deviceTransform.scale(1, -1);
1926 
1927         /* Create a BufferedImage to hold the band. We set the clip
1928          * of the band to be tight around the bits so that the
1929          * application can use it to figure what part of the
1930          * page needs to be drawn. The clip is never altered in
1931          * this method, but we do translate the band's coordinate
1932          * system so that the app will see the clip moving down the
1933          * page though it s always around the same set of pixels.
1934          */
1935         BufferedImage pBand = new BufferedImage(1, 1,
1936                                                 BufferedImage.TYPE_3BYTE_BGR);
1937 
1938         /* Have the app draw into a PeekGraphics object so we can
1939          * learn something about the needs of the print job.
1940          */
1941 
1942         PeekGraphics peekGraphics = createPeekGraphics(pBand.createGraphics(),
1943                                                        this);
1944 
1945         Rectangle2D.Double pageFormatArea =
1946             new Rectangle2D.Double(page.getImageableX(),
1947                                    page.getImageableY(),
1948                                    page.getImageableWidth(),
1949                                    page.getImageableHeight());
1950         peekGraphics.transform(scaleTransform);
1951         peekGraphics.translate(-getPhysicalPrintableX(paper) / xScale,
1952                                -getPhysicalPrintableY(paper) / yScale);
1953         peekGraphics.transform(new AffineTransform(page.getMatrix()));
1954         initPrinterGraphics(peekGraphics, pageFormatArea);
1955         AffineTransform pgAt = peekGraphics.getTransform();
1956 
1957         /* Update the information used to return a GraphicsConfiguration
1958          * for this printer device. It needs to be updated per page as
1959          * not all pages in a job may be the same size (different bounds)
1960          * The transform is the scaling transform as this corresponds to
1961          * the default transform for the device. The width and height are
1962          * those of the paper, not the page format, as we want to describe
1963          * the bounds of the device in its natural coordinate system of
1964          * device coordinate whereas a page format may be in a rotated context.
1965          */
1966         setGraphicsConfigInfo(scaleTransform,
1967                               paper.getWidth(), paper.getHeight());
1968         int pageResult = painter.print(peekGraphics, origPage, pageIndex);
1969         debug_println("pageResult "+pageResult);
1970         if (pageResult == Printable.PAGE_EXISTS) {
1971             debug_println("startPage "+pageIndex);
1972 
1973             /* We need to check if the paper size is changed.
1974              * Note that it is not sufficient to ask for the pageformat
1975              * of "pageIndex-1", since PageRanges mean that pages can be
1976              * skipped. So we have to look at the actual last paper size used.
1977              */
1978             Paper thisPaper = page.getPaper();
1979             boolean paperChanged =
1980                 previousPaper == null ||
1981                 thisPaper.getWidth() != previousPaper.getWidth() ||
1982                 thisPaper.getHeight() != previousPaper.getHeight();
1983             previousPaper = thisPaper;
1984 
1985             startPage(page, painter, pageIndex, paperChanged);
1986             Graphics2D pathGraphics = createPathGraphics(peekGraphics, this,
1987                                                          painter, page,
1988                                                          pageIndex);
1989 
1990             /* If we can convert the page directly to the
1991              * underlying graphics system then we do not
1992              * need to rasterize. We also may not need to
1993              * create the 'band' if all the pages can take
1994              * this path.
1995              */
1996             if (pathGraphics != null) {
1997                 pathGraphics.transform(scaleTransform);
1998                 // user (0,0) should be origin of page, not imageable area
1999                 pathGraphics.translate(-getPhysicalPrintableX(paper) / xScale,
2000                                        -getPhysicalPrintableY(paper) / yScale);
2001                 pathGraphics.transform(new AffineTransform(page.getMatrix()));
2002                 initPrinterGraphics(pathGraphics, pageFormatArea);
2003 
2004                 redrawList.clear();
2005 
2006                 AffineTransform initialTx = pathGraphics.getTransform();
2007 
2008                 painter.print(pathGraphics, origPage, pageIndex);
2009 
2010                 for (int i=0;i<redrawList.size();i++) {
2011                    GraphicsState gstate = (GraphicsState)redrawList.get(i);
2012                    pathGraphics.setTransform(initialTx);
2013                    ((PathGraphics)pathGraphics).redrawRegion(
2014                                                          gstate.region,
2015                                                          gstate.sx,
2016                                                          gstate.sy,
2017                                                          gstate.theClip,
2018                                                          gstate.theTransform);
2019                 }
2020 
2021             /* This is the banded-raster printing loop.
2022              * It should be moved into its own method.
2023              */
2024             } else {
2025                 BufferedImage band = cachedBand;
2026                 if (cachedBand == null ||
2027                     bandWidth != cachedBandWidth ||
2028                     bandHeight != cachedBandHeight) {
2029                     band = new BufferedImage(bandWidth, bandHeight,
2030                                              BufferedImage.TYPE_3BYTE_BGR);
2031                     cachedBand = band;
2032                     cachedBandWidth = bandWidth;
2033                     cachedBandHeight = bandHeight;
2034                 }
2035                 Graphics2D bandGraphics = band.createGraphics();
2036 
2037                 Rectangle2D.Double clipArea =
2038                     new Rectangle2D.Double(0, 0, bandWidth, bandHeight);
2039 
2040                 initPrinterGraphics(bandGraphics, clipArea);
2041 
2042                 ProxyGraphics2D painterGraphics =
2043                     new ProxyGraphics2D(bandGraphics, this);
2044 
2045                 Graphics2D clearGraphics = band.createGraphics();
2046                 clearGraphics.setColor(Color.white);
2047 
2048                 /* We need the actual bits of the BufferedImage to send to
2049                  * the native Window's code. 'data' points to the actual
2050                  * pixels. Right now these are in ARGB format with 8 bits
2051                  * per component. We need to use a monochrome BufferedImage
2052                  * for monochrome printers when this is supported by
2053                  * BufferedImage. FIX
2054                  */
2055                 ByteInterleavedRaster tile = (ByteInterleavedRaster)band.getRaster();
2056                 byte[] data = tile.getDataStorage();
2057 
2058                 /* Loop over the page moving our band down the page,
2059                  * calling the app to render the band, and then send the band
2060                  * to the printer.
2061                  */
2062                 int deviceBottom = deviceTop + deviceAreaHeight;
2063 
2064                 /* device's printable x,y is really addressable origin
2065                  * we address relative to media origin so when we print a
2066                  * band we need to adjust for the different methods of
2067                  * addressing it.
2068                  */
2069                 int deviceAddressableX = (int)getPhysicalPrintableX(paper);
2070                 int deviceAddressableY = (int)getPhysicalPrintableY(paper);
2071 
2072                 for (int bandTop = 0; bandTop <= deviceAreaHeight;
2073                      bandTop += bandHeight)
2074                 {
2075 
2076                     /* Put the band back into device space and
2077                      * erase the contents of the band.
2078                      */
2079                     clearGraphics.fillRect(0, 0, bandWidth, bandHeight);
2080 
2081                     /* Put the band into the correct location on the
2082                      * page. Once the band is moved we translate the
2083                      * device transform so that the band will move down
2084                      * the page on the next iteration of the loop.
2085                      */
2086                     bandGraphics.setTransform(uniformTransform);
2087                     bandGraphics.transform(deviceTransform);
2088                     deviceTransform.translate(0, -bandHeight);
2089 
2090                     /* Switch the band from device space to user,
2091                      * 72 dpi, space.
2092                      */
2093                     bandGraphics.transform(scaleTransform);
2094                     bandGraphics.transform(new AffineTransform(page.getMatrix()));
2095 
2096                     Rectangle clip = bandGraphics.getClipBounds();
2097                     clip = pgAt.createTransformedShape(clip).getBounds();
2098 
2099                     if ((clip == null) || peekGraphics.hitsDrawingArea(clip) &&
2100                         (bandWidth > 0 && bandHeight > 0)) {
2101 
2102                         /* if the client has specified an imageable X or Y
2103                          * which is off than the physically addressable
2104                          * area of the page, then we need to adjust for that
2105                          * here so that we pass only non -ve band coordinates
2106                          * We also need to translate by the adjusted amount
2107                          * so that printing appears in the correct place.
2108                          */
2109                         int bandX = deviceLeft - deviceAddressableX;
2110                         if (bandX < 0) {
2111                             bandGraphics.translate(bandX/xScale,0);
2112                             bandX = 0;
2113                         }
2114                         int bandY = deviceTop + bandTop - deviceAddressableY;
2115                         if (bandY < 0) {
2116                             bandGraphics.translate(0,bandY/yScale);
2117                             bandY = 0;
2118                         }
2119                         /* Have the app's painter image into the band
2120                          * and then send the band to the printer.
2121                          */
2122                         painterGraphics.setDelegate((Graphics2D) bandGraphics.create());
2123                         painter.print(painterGraphics, origPage, pageIndex);
2124                         painterGraphics.dispose();
2125                         printBand(data, bandX, bandY, bandWidth, bandHeight);
2126                     }
2127                 }
2128 
2129                 clearGraphics.dispose();
2130                 bandGraphics.dispose();
2131 
2132             }
2133             debug_println("calling endPage "+pageIndex);
2134             endPage(page, painter, pageIndex);
2135         }
2136 
2137         return pageResult;
2138     }
2139 
2140     /**
2141      * If a print job is in progress, print() has been
2142      * called but has not returned, then this signals
2143      * that the job should be cancelled and the next
2144      * chance. If there is no print job in progress then
2145      * this call does nothing.
2146      */
2147     public void cancel() {
2148         synchronized (this) {
2149             if (performingPrinting) {
2150                 userCancelled = true;
2151             }
2152             notify();
2153         }
2154     }
2155 
2156     /**
2157      * Returns true is a print job is ongoing but will
2158      * be cancelled and the next opportunity. false is
2159      * returned otherwise.
2160      */
2161     public boolean isCancelled() {
2162 
2163         boolean cancelled = false;
2164 
2165         synchronized (this) {
2166             cancelled = (performingPrinting && userCancelled);
2167             notify();
2168         }
2169 
2170         return cancelled;
2171     }
2172 
2173     /**
2174      * Return the Pageable describing the pages to be printed.
2175      */
2176     protected Pageable getPageable() {
2177         return mDocument;
2178     }
2179 
2180     /**
2181      * Examine the metrics captured by the
2182      * <code>PeekGraphics</code> instance and
2183      * if capable of directly converting this
2184      * print job to the printer's control language
2185      * or the native OS's graphics primitives, then
2186      * return a <code>PathGraphics</code> to perform
2187      * that conversion. If there is not an object
2188      * capable of the conversion then return
2189      * <code>null</code>. Returning <code>null</code>
2190      * causes the print job to be rasterized.
2191      */
2192     protected Graphics2D createPathGraphics(PeekGraphics graphics,
2193                                             PrinterJob printerJob,
2194                                             Printable painter,
2195                                             PageFormat pageFormat,
2196                                             int pageIndex) {
2197 
2198         return null;
2199     }
2200 
2201     /**
2202      * Create and return an object that will
2203      * gather and hold metrics about the print
2204      * job. This method is passed a <code>Graphics2D</code>
2205      * object that can be used as a proxy for the
2206      * object gathering the print job matrics. The
2207      * method is also supplied with the instance
2208      * controlling the print job, <code>printerJob</code>.
2209      */
2210     protected PeekGraphics createPeekGraphics(Graphics2D graphics,
2211                                               PrinterJob printerJob) {
2212 
2213         return new PeekGraphics(graphics, printerJob);
2214     }
2215 
2216     /**
2217      * Configure the passed in Graphics2D so that
2218      * is contains the defined initial settings
2219      * for a print job. These settings are:
2220      *      color:  black.
2221      *      clip:   <as passed in>
2222      */
2223     void initPrinterGraphics(Graphics2D g, Rectangle2D clip) {
2224 
2225         g.setClip(clip);
2226         g.setPaint(Color.black);
2227     }
2228 
2229 
2230    /**
2231     * User dialogs should disable "File" buttons if this returns false.
2232     *
2233     */
2234     public boolean checkAllowedToPrintToFile() {
2235         try {
2236             throwPrintToFile();
2237             return true;
2238         } catch (SecurityException e) {
2239             return false;
2240         }
2241     }
2242 
2243     /**
2244      * Break this out as it may be useful when we allow API to
2245      * specify printing to a file. In that case its probably right
2246      * to throw a SecurityException if the permission is not granted
2247      */
2248     private void throwPrintToFile() {
2249         SecurityManager security = System.getSecurityManager();
2250         if (security != null) {
2251             if (printToFilePermission == null) {
2252                 printToFilePermission =
2253                     new FilePermission("<<ALL FILES>>", "read,write");
2254             }
2255             security.checkPermission(printToFilePermission);
2256         }
2257     }
2258 
2259     /* On-screen drawString renders most control chars as the missing glyph
2260      * and have the non-zero advance of that glyph.
2261      * Exceptions are \t, \n and \r which are considered zero-width.
2262      * This is a utility method used by subclasses to remove them so we
2263      * don't have to worry about platform or font specific handling of them.
2264      */
2265     protected String removeControlChars(String s) {
2266         char[] in_chars = s.toCharArray();
2267         int len = in_chars.length;
2268         char[] out_chars = new char[len];
2269         int pos = 0;
2270 
2271         for (int i = 0; i < len; i++) {
2272             char c = in_chars[i];
2273             if (c > '\r' || c < '\t' || c == '\u000b' || c == '\u000c')  {
2274                out_chars[pos++] = c;
2275             }
2276         }
2277         if (pos == len) {
2278             return s; // no need to make a new String.
2279         } else {
2280             return new String(out_chars, 0, pos);
2281         }
2282     }
2283 }